Skip to content

Encrypt on writing, not only on reading - #8

Merged
tannevaled merged 1 commit into
mainfrom
encrypt-on-write
Aug 25, 2026
Merged

Encrypt on writing, not only on reading#8
tannevaled merged 1 commit into
mainfrom
encrypt-on-write

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

The reader has understood every revision of the standard security handler
since its third wave; the writer could only produce files in the clear. Now it
can protect one: AES-256 by default, AES-128 for readers older than 2008, with
a user password, an owner password and the permissions a viewer is asked to
respect.

Order matters twice over. A stream is compressed and then encrypted, never the
other way, because encrypted bytes do not compress. And the /Encrypt dictionary
and the cross-reference stream are written untouched, since a reader needs both
before it has a key.

Packing and encryption meet correctly: objects inside an object stream are not
individually encrypted, the stream that holds them is — which falls out of
doing the work where an object is written rather than where it is handed over.

Two things this found. An owner password nobody set was written as the empty
one, so any password at all opened the file with every permission — the exact
opposite of asking for encryption. It is now the user's password, which is what
the pre-2.0 algorithms have always done and what the AES-256 path was missing.
And randomness that fails must refuse the file rather than hand back the bytes
it could not protect; the source of randomness is a variable so a test can take
it away and watch every draw give up in turn.

Measured on the corpus: every one of the 118 833 files is written behind a
password in both methods and read back with the user's and the owner's — all
118 833 match in both, and all 237 666 written files refuse to open without a
password.

An encrypted file is deliberately not reproducible: encryption needs
randomness, so the writer stops being a function of its input, and only there.

Exact 100% statement coverage, go vet clean, nine cross-compilation targets.

The reader has understood every revision of the standard security handler
since its third wave; the writer could only produce files in the clear. Now it
can protect one: AES-256 by default, AES-128 for readers older than 2008, with
a user password, an owner password and the permissions a viewer is asked to
respect.

Order matters twice over. A stream is compressed and then encrypted, never the
other way, because encrypted bytes do not compress. And the /Encrypt dictionary
and the cross-reference stream are written untouched, since a reader needs both
before it has a key.

Packing and encryption meet correctly: objects inside an object stream are not
individually encrypted, the stream that holds them is — which falls out of
doing the work where an object is written rather than where it is handed over.

Two things this found. An owner password nobody set was written as the empty
one, so any password at all opened the file with every permission — the exact
opposite of asking for encryption. It is now the user's password, which is what
the pre-2.0 algorithms have always done and what the AES-256 path was missing.
And randomness that fails must refuse the file rather than hand back the bytes
it could not protect; the source of randomness is a variable so a test can take
it away and watch every draw give up in turn.

Measured on the corpus: every one of the 118 833 files is written behind a
password in both methods and read back with the user's and the owner's — all
118 833 match in both, and all 237 666 written files refuse to open without a
password.

An encrypted file is deliberately not reproducible: encryption needs
randomness, so the writer stops being a function of its input, and only there.

Exact 100% statement coverage, go vet clean, nine cross-compilation targets.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 3d7bb97 into main Aug 25, 2026
1 check passed
@tannevaled
tannevaled deleted the encrypt-on-write branch August 25, 2026 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant